package com.meshnetics.gb.stack.mac.type;

import com.meshnetics.gb.type.DataValue;
import com.meshnetics.gb.type.impl.ByteUtils;
import com.meshnetics.gb.type.impl.custom.StructureDataValue;
import com.meshnetics.gb.type.impl.zcl.TypeUInt;
import com.meshnetics.gb.type.impl.zcl.UInt;

import java.nio.ByteBuffer;

/**
 * Packed representation of mac primitives' way of representation of addresses. MAC primitives in general represent
 * address and couple of panId and long or short address (but even only pan can be specified). In primitives form of
 * address is denoted as addressingMode field that is hided inside of this value.
 *
 * @author AKovylin
 */
public class DataAddress extends StructureDataValue {
    public static final UInt BROADCAST_SHORT_UINT =
            (UInt) TypeUInt.UINT_16.valueOf(new byte[]{(byte) 0xff, (byte) 0xff});

    public static final Short BROADCAST_SHORT = (Short) BROADCAST_SHORT_UINT.toObject(); // todo verify?

    public DataAddress(short panId) {
        this(new Short(panId));
    }

    public DataAddress(short panId, short address) {
        this(new Short(panId), new Short(address));
    }

    public DataAddress(short panId, long address) {
        this(new Short(panId), new Long(address));
    }

    public DataAddress(Short panId) {
        this((UInt) TypeUInt.UINT_16.fromObject(panId));
    }

    public DataAddress(Short panId, Short address) {
        this((UInt) TypeUInt.UINT_16.fromObject(panId),
                (UInt) TypeUInt.UINT_16.fromObject(address));
    }

    public DataAddress(Short panId, Long address) {
        this((UInt) TypeUInt.UINT_16.fromObject(panId),
                (UInt) TypeUInt.UINT_64.fromObject(address));
    }

    public DataAddress(UInt panId) {
        this(TypeDataAddress.TYPE, panId, null);
    }

    public DataAddress(UInt panId, UInt address) {
        this(TypeDataAddress.TYPE, panId, address);
    }

    DataAddress(TypeDataAddress type, UInt panId, UInt address) {
        super(type, chechAndPrepareValues(panId, address));
    }

    DataAddress(TypeDataAddress type, DataValue[] values) {
        super(type, values);
        if (values[1] != null && values[2] != null) {
            throw new IllegalArgumentException("only shortAddress or longAddress should be specified, not both");
        }
    }

    private static DataValue[] chechAndPrepareValues(UInt panId, UInt address) {
        if (panId == null) {
            throw new NullPointerException("panId is null");
        }
        if (address != null) {
            if (address.getType() != TypeUInt.UINT_16 &&
                    address.getType() != TypeUInt.UINT_64) {
                throw new IllegalArgumentException("address must be either uint16 or uint64");
            }
        }
        DataValue[] values = new DataValue[3];
        values[0] = panId;
        if (address == null) {
            values[1] = null;
            values[2] = null;
        } else if (address.getType() == TypeUInt.UINT_16) {
            values[1] = address;
            values[2] = null;
        } else {
            values[1] = null;
            values[2] = address;
        }
        return values;
    }

    public UInt getPanIdType() {
        return (UInt) getFieldValue(0);
    }

    public UInt getShortAddressType() {
        return (UInt) getFieldValue(1);
    }

    public UInt getLongAddressType() {
        return (UInt) getFieldValue(2);
    }

    public Short getPanId() {
        return (Short) getFieldValue(0).toObject();
    }

    public Short getShortAddress() {
        return (Short) getFieldValue(1).toObject();
    }

    public Long getLongAddress() {
        return (Long) getFieldValue(2).toObject();
    }

    public boolean isNoAddress() {
        return getFieldValue(1) == null && getFieldValue(2) == null;
    }

    public boolean isShortAddress() {
        return getFieldValue(1) != null;
    }

    public boolean isExtendedAddress() {
        return getFieldValue(2) != null;
    }

    public UInt getAddressType() {
        return isShortAddress() ? getShortAddressType() : getLongAddressType();
    }

    public int getDataSize() {
        return 1 + 2 + (isShortAddress() ? 2 : (isExtendedAddress() ? 8 : 0));
    }

    public Object toObject() {
        throw new UnsupportedOperationException("not implemented yet");
        // todo reimplement
    }

    public void serialize(ByteBuffer bb) {
        ByteUtils.ensureNotNull(bb);
        if (isNoAddress()) {
            bb.put((byte) TypeDataAddress.ADDR_MODE_PANID);
            getFieldValue(0).serialize(bb);
        } else if (isShortAddress()) {
            bb.put((byte) TypeDataAddress.ADDR_MODE_SHORT);
            getFieldValue(0).serialize(bb);
            getFieldValue(1).serialize(bb);
        } else {
            bb.put((byte) TypeDataAddress.ADDR_MODE_LONG);
            getFieldValue(0).serialize(bb);
            getFieldValue(2).serialize(bb);
        }
    }
}
